package sf.dynamicsql;

import org.mybatis.dynamic.sql.delete.DeleteModel;
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
import org.mybatis.dynamic.sql.insert.GeneralInsertModel;
import org.mybatis.dynamic.sql.insert.InsertSelectModel;
import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;
import org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider;
import org.mybatis.dynamic.sql.select.SelectModel;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.update.UpdateModel;
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
import sf.common.wrapper.Page;
import sf.database.dialect.DBDialect;
import sf.database.jdbc.sql.Crud;
import sf.database.util.DBUtils;
import sf.database.util.SimpleSQLTemplate;
import sf.spring.util.Assert;

import java.sql.Connection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class DynmicSQLModelImpl implements DynmicSQLModelInf {
    private static DynmicSQLModelImpl instance = new DynmicSQLModelImpl();

    public static DynmicSQLModelImpl getInstance() {
        return instance;
    }

    private DynmicSQLModelImpl() {

    }


    @Override
    public int insert(Connection conn, List<String> pkeys, Map<String, Object> keyValues, GeneralInsertModel generalInsertModel) {
        GeneralInsertStatementProvider provider = generalInsertModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getInsertStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().insert(conn, pkeys, keyValues, sql, params);
    }


    @Override
    public int insertSelect(Connection conn, List<String> pkeys, Map<String, Object> keyValues, InsertSelectModel insertSelectModel) {
        InsertSelectStatementProvider provider = insertSelectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getInsertStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().insert(conn, pkeys, keyValues, sql, params);
    }

    @Override
    public int update(Connection conn, UpdateModel updateModel) {
        UpdateStatementProvider provider = updateModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getUpdateStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().execute(conn, sql, params);
    }

    @Override
    public int delete(Connection conn, DeleteModel deleteModel) {
        DeleteStatementProvider provider = deleteModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getDeleteStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().execute(conn, sql, params);
    }


    @Override
    public <T> Page<T> selectPage(Connection conn, long start, int limit, Class<T> beanClass, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().selectPage(conn, start, limit, beanClass, sql, params);
    }

    @Override
    public <T> Page<T> selectPageRaw(Connection conn, long start, int limit, Class<T> beanClass, SelectModel countSelectModel,
                                     SelectModel listSelectModel) {
        Assert.notNull(beanClass, "beanClass is null.");
        Assert.notNull(countSelectModel, "countSql is null.");
        Assert.notNull(listSelectModel, "listSql is null.");
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        SelectStatementProvider countProvider = countSelectModel.render(SQLRenderingStrategy.instance);
        String countSql = countProvider.getSelectStatement();
        Map<String, Object> countParameters = countProvider.getParameters();
        Object[] countParams = new Object[countParameters.size()];
        for (int i = 0; i < countParams.length; i++) {
            countParams[i] = countParameters.get("p" + (i + 1));
        }
        countSql = SimpleSQLTemplate.unwrapper(dialect, countSql);

        SelectStatementProvider listProvider = countSelectModel.render(SQLRenderingStrategy.instance);
        String listSql = listProvider.getSelectStatement();
        Map<String, Object> listParameters = listProvider.getParameters();
        Object[] listParams = new Object[listParameters.size()];
        for (int i = 0; i < listParams.length; i++) {
            listParams[i] = listParameters.get("p" + (i + 1));
        }
        listSql = SimpleSQLTemplate.unwrapper(dialect, listSql);

        return Crud.getInstance().getCrudSql().selectPageRaw(conn, start, limit, beanClass, countSql, countParams,
                listSql, listParams);
    }


    @Override
    public Object[] selectArray(Connection conn, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().selectArray(conn, sql, params);
    }


    @Override
    public <T> List<T> selectList(Connection conn, Class<T> beanClass, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().selectList(conn, beanClass, sql, params);
    }


    @Override
    public <T> List<T> selectList(Connection conn, Class<T> beanClass, SelectModel selectModel, long start, int limit) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        String pageSql = dialect.sqlPageList(new StringBuilder(sql), start, limit).toString();
        return Crud.getInstance().getCrudSql().selectList(conn, beanClass, pageSql, params);
    }


    @Override
    public <T> T selectOne(Connection conn, Class<T> beanClass, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().selectOne(conn, beanClass, sql, params);
    }


    @Override
    public List<Map<String, Object>> select(Connection conn, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        return Crud.getInstance().getCrudSql().select(conn, sql, params);
    }


    @Override
    public <T> void selectIterator(Connection conn, Consumer<Iterable<T>> ormIt, Class<T> returnClass, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        Crud.getInstance().getCrudSql().selectIterator(conn, ormIt, returnClass, false, sql, params);
    }


    @Override
    public <T> void selectStream(Connection conn, Consumer<Stream<T>> ormStream, Class<T> returnClass, SelectModel selectModel) {
        SelectStatementProvider provider = selectModel.render(SQLRenderingStrategy.instance);
        String sql = provider.getSelectStatement();
        Map<String, Object> parameterMaps = provider.getParameters();
        Object[] params = new Object[parameterMaps.size()];
        for (int i = 0; i < params.length; i++) {
            params[i] = parameterMaps.get("p" + (i + 1));
        }
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        sql = SimpleSQLTemplate.unwrapper(dialect, sql);
        Crud.getInstance().getCrudSql().selectStream(conn, ormStream, returnClass, false, sql, params);
    }
}
